﻿namespace VividKit.Controls
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using VividKit.PresentationModel.Navigation;
	using Windows.Foundation.Metadata;
	using Windows.System;
	using Windows.UI.Core;
	using Windows.UI.ViewManagement;
	using Windows.UI.Xaml;
	using Windows.UI.Xaml.Controls;
	using Windows.UI.Xaml.Navigation;

	[WebHostHidden]
	public class NavigablePage : Page
	{
		private bool isNewInstance;
		private string pageKey;

		/// <summary>
		/// Initializes a new instance of the <see cref="NavigablePage"/> class.
		/// </summary>
		public NavigablePage()
		{
			this.isNewInstance = true;

			// When this page is part of the visual tree make two changes:
			// 1) Map application view state to visual state for the page
			// 2) Handle keyboard and mouse navigation requests
			this.Loaded += (sender, e) =>
			{
				// Keyboard and mouse navigation only apply when occupying the entire window
				if (this.ActualHeight == Window.Current.Bounds.Height &&
					this.ActualWidth == Window.Current.Bounds.Width)
				{
					// Listen to the window directly so focus isn't required
					Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated +=
						HandleCoreDispatcherAcceleratorKeyActivated;
					Window.Current.CoreWindow.PointerPressed +=
						this.HandleCoreWindowPointerPressed;
				}
			};

			// Undo the same changes when the page is no longer visible
			this.Unloaded += (sender, e) =>
			{
				Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated -=
					HandleCoreDispatcherAcceleratorKeyActivated;
				Window.Current.CoreWindow.PointerPressed -=
					this.HandleCoreWindowPointerPressed;
			};
		}

		/// <summary>
		/// Called when a page is no longer the active page in a frame.
		/// </summary>
		/// <param name="e">An object that contains the event data.</param>
		protected override void OnNavigatedFrom(NavigationEventArgs e)
		{
			var navigable = this.DataContext as INavigable;
			if (navigable != null)
			{
				if (e.NavigationMode != NavigationMode.Back)
				{
					HandleSerializing(navigable);
				}

				navigable.OnNavigatedFrom(null);
			}
		}

		/// <summary>
		/// Called when a page becomes the active page in a frame.
		/// </summary>
		/// <param name="e">An object that contains the event data.</param>
		protected override void OnNavigatedTo(NavigationEventArgs e)
		{
			var navigable = this.DataContext as INavigable;
			if (navigable != null)
			{
				navigable.OnNavigatedTo(e.NavigationMode == NavigationMode.Back);

				if (this.isNewInstance == false || HandleDeserializing(navigable, e) == false)
				{
					var tombstone = navigable as ITombstone;
					if (tombstone != null)
					{
						IDictionary<string, object> state = null;

						if (e.Parameter != null)
						{
							state = new Dictionary<string, object>
							{
								{ String.Empty, e.Parameter }
							};
						}

						tombstone.OnDeserializing(state ?? new Dictionary<string, object>());
					}
				}
			}

			this.isNewInstance = false;
		}

		/// <summary>
		/// Handles the page deserializing.
		/// </summary>
		/// <param name="navigable">The navigator view model.</param>
		private bool HandleDeserializing(INavigable navigable, NavigationEventArgs e)
		{
			var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
			this.pageKey = "Page-" + this.Frame.BackStackDepth;

			if (e.NavigationMode == NavigationMode.New)
			{
				// Clear existing state for forward navigation when adding a new page to the navigation stack
				var nextPageKey = this.pageKey;
				int nextPageIndex = this.Frame.BackStackDepth;
				while (frameState.Remove(nextPageKey))
				{
					nextPageIndex++;
					nextPageKey = "Page-" + nextPageIndex;
				}
			}
			else
			{
				var tombstone = navigable as ITombstone;
				if (tombstone != null)
				{
					// Pass the navigation parameter and preserved page state to the page, using the same strategy
					// for loading suspended state and recreating pages discarded from cache
					var state = (Dictionary<string, object>)frameState[this.pageKey];
					var hasState = state.Any();

					if (hasState)
					{
						tombstone.OnDeserializing(state);
					}

					return hasState;
				}
			}

			// New page, no state, or no support for managing state
			return false;
		}

		/// <summary>
		/// Handles the page serializing.
		/// </summary>
		/// <param name="navigable">The navigator view model.</param>
		/// <param name="e">The <see cref="Windows.UI.Xaml.Navigation.NavigationEventArgs" /> instance
		/// containing the event data.</param>
		private void HandleSerializing(INavigable navigable)
		{
			var tombstone = navigable as ITombstone;
			if (tombstone != null)
			{
				var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
				var pageState = new Dictionary<String, Object>();

				tombstone.OnSerializing(pageState);

				frameState[this.pageKey] = pageState;
			}
		}

		#region Navigation support

		/// <summary>
		/// Invoked as an event handler to navigate backward in the navigation stack associated with this
		/// page's <see cref="Frame"/>.
		/// </summary>
		/// <param name="sender">Instance that triggered the event.</param>
		/// <param name="e">Event data describing the conditions that led to the event.</param>
		protected virtual void GoBack(object sender, RoutedEventArgs e)
		{
			// Use the navigation frame to return to the previous page
			if (this.Frame != null && this.Frame.CanGoBack) this.Frame.GoBack();
		}

		/// <summary>
		/// Invoked as an event handler to navigate forward in the navigation stack associated with this
		/// page's <see cref="Frame"/>.
		/// </summary>
		/// <param name="sender">Instance that triggered the event.</param>
		/// <param name="e">Event data describing the conditions that led to the event.</param>
		protected virtual void GoForward(object sender, RoutedEventArgs e)
		{
			// Use the navigation frame to move to the next page
			if (this.Frame != null && this.Frame.CanGoForward) this.Frame.GoForward();
		}

		/// <summary>
		/// Invoked as an event handler to navigate backward in the page's associated <see cref="Frame"/>
		/// until it reaches the top of the navigation stack.
		/// </summary>
		/// <param name="sender">Instance that triggered the event.</param>
		/// <param name="e">Event data describing the conditions that led to the event.</param>
		protected virtual void GoHome(object sender, RoutedEventArgs e)
		{
			// Use the navigation frame to return to the topmost page
			if (this.Frame != null)
			{
				while (this.Frame.CanGoBack) this.Frame.GoBack();
			}
		}

		/// <summary>
		/// Invoked on every keystroke, including system keys such as Alt key combinations, when this page
		/// is active and occupies the entire window. Used to detect keyboard navigation between pages
		/// even when the page itself doesn't have focus.
		/// </summary>
		/// <param name="sender">Instance that triggered the event.</param>
		/// <param name="args">Event data describing the conditions that led to the event.</param>
		private void HandleCoreDispatcherAcceleratorKeyActivated(CoreDispatcher sender, AcceleratorKeyEventArgs args)
		{
			var virtualKey = args.VirtualKey;

			// Only investigate further when Left, Right, or the dedicated Previous or Next keys are pressed
			if ((args.EventType == CoreAcceleratorKeyEventType.SystemKeyDown ||
				args.EventType == CoreAcceleratorKeyEventType.KeyDown) &&
				(virtualKey == VirtualKey.Left || virtualKey == VirtualKey.Right ||
				(int)virtualKey == 166 || (int)virtualKey == 167))
			{
				var coreWindow = Window.Current.CoreWindow;
				var downState = CoreVirtualKeyStates.Down;
				bool menuKey = (coreWindow.GetKeyState(VirtualKey.Menu) & downState) == downState;
				bool controlKey = (coreWindow.GetKeyState(VirtualKey.Control) & downState) == downState;
				bool shiftKey = (coreWindow.GetKeyState(VirtualKey.Shift) & downState) == downState;
				bool noModifiers = !menuKey && !controlKey && !shiftKey;
				bool onlyAlt = menuKey && !controlKey && !shiftKey;

				if (((int)virtualKey == 166 && noModifiers) ||
					(virtualKey == VirtualKey.Left && onlyAlt))
				{
					// When the previous key or Alt+Left are pressed navigate back
					args.Handled = true;
					this.GoBack(this, new RoutedEventArgs());
				}
				else if (((int)virtualKey == 167 && noModifiers) ||
					(virtualKey == VirtualKey.Right && onlyAlt))
				{
					// When the next key or Alt+Right are pressed navigate forward
					args.Handled = true;
					this.GoForward(this, new RoutedEventArgs());
				}
			}
		}

		/// <summary>
		/// Invoked on every mouse click, touch screen tap, or equivalent interaction when this page is
		/// active and occupies the entire window. Used to detect browser-style next and previous mouse
		/// button clicks to navigate between pages.
		/// </summary>
		/// <param name="sender">Instance that triggered the event.</param>
		/// <param name="args">Event data describing the conditions that led to the event.</param>
		private void HandleCoreWindowPointerPressed(CoreWindow sender, PointerEventArgs args)
		{
			var properties = args.CurrentPoint.Properties;

			// Ignore button chords with the left, right, and middle buttons
			if (properties.IsLeftButtonPressed || properties.IsRightButtonPressed ||
				properties.IsMiddleButtonPressed) return;

			// If back or forward are pressed (but not both) navigate appropriately
			bool backPressed = properties.IsXButton1Pressed;
			bool forwardPressed = properties.IsXButton2Pressed;
			if (backPressed ^ forwardPressed)
			{
				args.Handled = true;
				if (backPressed) this.GoBack(this, new RoutedEventArgs());
				if (forwardPressed) this.GoForward(this, new RoutedEventArgs());
			}
		}

		#endregion Navigation support
	}
}